﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using blackhouse.BlackApart.Data;
using System.Globalization;
using blackhouse.BlackApart.Business.Apartments;
using blackhouse.BlackApart.Business.Reservations;

namespace blackhouse.BlackApart.Business.Opinions {
    public class DaOpinion : IOpinion {

        #region Fields

        private IOpinionsDataAccess dataAccess;
        private ImpOpinion instance;

        #endregion

        #region Properties

        public int Id {
            get { return this.instance.Id; }
            private set { this.instance.Id = value; }
        }

        public string InPlusOpinion {
            get { return this.instance.InPlusOpinion; }
            set { this.instance.InPlusOpinion = value; }
        }

        public string InMinusOpinion {
            get { return this.instance.InMinusOpinion; }
            set { this.instance.InMinusOpinion = value; }
        }

        public string Answer {
            get { return this.instance.Answer; }
            set { this.instance.Answer = value; }
        }

        public DateTime PublishedDate {
            get { return this.instance.PublishedDate; }
            set { this.instance.PublishedDate = value; }
        }

        public AuthorOpinion Publisher {
            get { return this.instance.Publisher; }
            set { this.instance.Publisher = value; }
        }

        public bool IsPublic {
            get { return this.instance.IsPublic; }
            private set { this.instance.IsPublic = value; }
        }

        #endregion

        #region Constructors

        public DaOpinion(IOpinionsDataAccess iOpinionsDataAccess) {
            this.dataAccess = iOpinionsDataAccess;
            this.instance = new ImpOpinion();
        }

        public DaOpinion(IOpinionsDataAccess iOpinionsDataAccess, int opinionId) {
            this.dataAccess = iOpinionsDataAccess;
            this.instance = this.ReadOpinion(opinionId);
            this.ReadOpinionDetails();
            if (this.instance == null) this.instance = new ImpOpinion();
        }

        private DaOpinion(IOpinionsDataAccess iOpinionsDataAccess, OpinionData data) : this(iOpinionsDataAccess, data, new OpinionCategoryValueData[0]) { }

        private DaOpinion(IOpinionsDataAccess iOpinionsDataAccess, OpinionData data, OpinionCategoryValueData[] details) {
            this.dataAccess = iOpinionsDataAccess;
            this.instance = this.Convert(data);
            if (this.instance == null) this.instance = new ImpOpinion();
            foreach (OpinionCategoryValueData value in details) {
                this.AddOpinionDetailValue(value.CategoryId, value.Value);
            }
        }

        #endregion

        #region Methods

        private void ReadOpinionDetails() {
            Dictionary<int, float> details = this.dataAccess.GetOpinionDetails(this.Id);
            foreach (KeyValuePair<int, float> item in details) {
                this.instance.AddOpinionDetailValue(item.Key, item.Value);
            }
        }

        public bool Save(int userId) {
            if (this.instance.Save(userId)) {
                int id = this.dataAccess.SaveOpinion(this.Id, this.InPlusOpinion, this.InMinusOpinion, this.PublishedDate, this.Publisher.Category, this.Publisher.FirstName, this.Publisher.LastName, this.Publisher.Country, CultureInfo.CurrentCulture.Name, userId);
                if (id > 0) {
                    this.Id = id;
                    foreach (KeyValuePair<int, float> item in this.instance.OpinionDetails)
                        this.dataAccess.AddOpinionDetailValue(this.Id, item.Key, item.Value);
                    return true;
                }
            }
            return false;
        }

        public bool Moderate(int userId) {
            if (this.Id > 0) {
                int id = this.dataAccess.SaveModerateOpinion(this.Id, this.InPlusOpinion, this.InMinusOpinion, this.Answer, userId);
                return this.Id == id;
            }
            else return false;
        }

        public void AddOpinionDetailValue(int categoryId, float val) {
            this.instance.AddOpinionDetailValue(categoryId, val);
        }

        public float GetAverageRating() {
            return this.instance.GetAverageRating();
        }

        public float GetOpinionDetailValue(int categoryId) {
            return this.instance.GetOpinionDetailValue(categoryId);
        }

        private ImpOpinion ReadOpinion(int opinionId) {
            OpinionData data = this.dataAccess.GetOpinion(opinionId);
            if (data.Id == opinionId)
                return this.Convert(data);
            else return null;
        }

        private ImpOpinion Convert(OpinionData data) {
            ImpOpinion opinion = new ImpOpinion();
            opinion.Id = data.Id;
            opinion.InMinusOpinion = data.InMinusOpinion;
            opinion.InPlusOpinion = data.InPlusOpinion;
            opinion.PublishedDate = data.PublishedDate;
            opinion.ApartmentGroupId = data.ApartmentType;
            opinion.Publisher = new AuthorOpinion();
            opinion.Publisher.Category = data.PublisherCategory;
            opinion.Publisher.Country = data.PublisherCountry;
            opinion.Publisher.FirstName = data.PublisherFirstName;
            opinion.Publisher.LastName = data.PublisherLastName;
            opinion.IsPublic = data.IsPublic;
            opinion.Answer = data.Answer;
            return opinion;
        }

        public IOpinion[] GetOpinions() {
            return this.GetOpinions(false);
        }

        public IOpinion[] GetOpinions(bool includeNonPublic) {
            OpinionData[] headers = this.dataAccess.GetOpinionsHeader(includeNonPublic);
            OpinionCategoryValueData[] details = this.dataAccess.GetOpinionsDetails(includeNonPublic);
            Dictionary<int, IOpinion> opinions = new Dictionary<int, IOpinion>(headers.Length);
            for (int i = 0; i < headers.Length; i++)
                opinions.Add(headers[i].Id, this.Convert(headers[i]));
            for (int xd = 0; xd < details.Length; xd++) {
                opinions[details[xd].OpinionId].AddOpinionDetailValue(details[xd].CategoryId, details[xd].Value);
            }
            if (includeNonPublic)
                return opinions.Values.OrderBy(o => o.IsPublic).ThenByDescending(o => o.PublishedDate).ToArray();
            else
                return opinions.Values.OrderByDescending(o => o.PublishedDate).ToArray();
        }

        public void Publish(int userId) {
            this.dataAccess.PublishOpinion(this.Id, userId);
        }

        public int FindReservationToAddOpinion(int apartmentTypeId) {
            return this.dataAccess.FindReservationToAddOpinion(apartmentTypeId);
        }

        public IApartmentGroup GetAssociatedApartmentGroup() {
            return this.instance.GetAssociatedApartmentGroup();
        }

        public int GetAssociatedApartmentGroupId() {
            return this.instance.GetAssociatedApartmentGroupId();
        }

        public IOpinion[] GetSimilarOpinions(int opinionId, TimeSpan timeWatch) {
            if (this.instance.SimilarOpinions == null) {
                IOpinion[] allOpinions = this.GetOpinions(true);
                IOpinion watchedOpinion = allOpinions.FirstOrDefault(o => o.Id == opinionId);
                if (watchedOpinion != null) {
                    int wApartId = watchedOpinion.GetAssociatedApartmentGroupId();
                    DateTime fD = watchedOpinion.PublishedDate.Subtract(timeWatch);
                    DateTime tD = watchedOpinion.PublishedDate.Add(timeWatch);
                    var q = from o in allOpinions where o.IsPublic && o.Id != watchedOpinion.Id && o.GetAssociatedApartmentGroupId() == wApartId && o.PublishedDate >= fD && o.PublishedDate <= tD select o;
                    this.instance.SimilarOpinions = q.ToArray();
                }
            }
            return this.instance.SimilarOpinions;
        }

        public void Visit(IOpinionVisitor visitor) {
            this.instance.Visit(visitor);
        }

        public List<IWebReservation> FindInterviewerReservation() {
            return this.instance.FindInterviewerReservation();
        }

        #endregion

    }
}
